#ifndef SCANNER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define SCANNER_H_62B23520_7C8E_11DE_8A39_0800200C9A66

#if defined(_MSC_VER) ||                                            \
    (defined(__GNUC__) && (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || \
     (__GNUC__ >= 4)) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif

#include <cstddef>
#include <ios>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>

#include "ptr_vector.h"
#include "stream.h"
#include "token.h"
#include "yaml-cpp/mark.h"

namespace YAML {
class Node;
class RegEx;

/**
 * A scanner transforms a stream of characters into a stream of tokens.
 */
class Scanner {
public:
    explicit Scanner(std::istream& in);
    ~Scanner();

    /** Returns true if there are no more tokens to be read. */
    bool empty();

    /** Removes the next token in the queue. */
    void pop();

    /** Returns, but does not remove, the next token in the queue. */
    Token& peek();

    /** Returns the current mark in the input stream. */
    Mark mark() const;

private:
    struct IndentMarker {
        enum INDENT_TYPE { MAP, SEQ, NONE };
        enum STATUS { VALID, INVALID, UNKNOWN };
        IndentMarker(int column_, INDENT_TYPE type_)
            : column(column_), type(type_), status(VALID),
              pStartToken(nullptr) {}

        int column;
        INDENT_TYPE type;
        STATUS status;
        Token* pStartToken;
    };

    enum FLOW_MARKER { FLOW_MAP, FLOW_SEQ };

private:
    // scanning

    /**
     * Scans until there's a valid token at the front of the queue, or the queue
     * is empty. The state can be checked by {@link #empty}, and the next token
     * retrieved by {@link #peek}.
     */
    void EnsureTokensInQueue();

    /**
     * The main scanning function; this method branches out to scan whatever the
     * next token should be.
     */
    void ScanNextToken();

    /** Eats the input stream until it reaches the next token-like thing. */
    void ScanToNextToken();

    /** Sets the initial conditions for starting a stream. */
    void StartStream();

    /** Closes out the stream, finish up, etc. */
    void EndStream();

    Token* PushToken(Token::TYPE type);

    bool InFlowContext() const { return !m_flows.empty(); }
    bool InBlockContext() const { return m_flows.empty(); }
    std::size_t GetFlowLevel() const { return m_flows.size(); }

    Token::TYPE GetStartTokenFor(IndentMarker::INDENT_TYPE type) const;

    /**
     * Pushes an indentation onto the stack, and enqueues the proper token
     * (sequence start or mapping start).
     *
     * @return the indent marker it generates (if any).
     */
    IndentMarker* PushIndentTo(int column, IndentMarker::INDENT_TYPE type);

    /**
     * Pops indentations off the stack until it reaches the current indentation
     * level, and enqueues the proper token each time. Then pops all invalid
     * indentations off.
     */
    void PopIndentToHere();

    /**
     * Pops all indentations (except for the base empty one) off the stack, and
     * enqueues the proper token each time.
     */
    void PopAllIndents();

    /** Pops a single indent, pushing the proper token. */
    void PopIndent();
    int GetTopIndent() const;

    // checking input
    bool CanInsertPotentialSimpleKey() const;
    bool ExistsActiveSimpleKey() const;
    void InsertPotentialSimpleKey();
    void InvalidateSimpleKey();
    bool VerifySimpleKey();
    void PopAllSimpleKeys();

    /**
     * Throws a ParserException with the current token location (if available),
     * and does not parse any more tokens.
     */
    void ThrowParserException(const std::string& msg) const;

    bool IsWhitespaceToBeEaten(char ch);

    /**
     * Returns the appropriate regex to check if the next token is a value
     * token.
     */
    const RegEx& GetValueRegex() const;

    struct SimpleKey {
        SimpleKey(const Mark& mark_, std::size_t flowLevel_);

        void Validate();
        void Invalidate();

        Mark mark;
        std::size_t flowLevel;
        IndentMarker* pIndent;
        Token *pMapStart, *pKey;
    };

    // and the tokens
    void ScanDirective();
    void ScanDocStart();
    void ScanDocEnd();
    void ScanBlockSeqStart();
    void ScanBlockMapSTart();
    void ScanBlockEnd();
    void ScanBlockEntry();
    void ScanFlowStart();
    void ScanFlowEnd();
    void ScanFlowEntry();
    void ScanKey();
    void ScanValue();
    void ScanAnchorOrAlias();
    void ScanTag();
    void ScanPlainScalar();
    void ScanQuotedScalar();
    void ScanBlockScalar();

private:
    // the stream
    Stream INPUT;

    // the output (tokens)
    std::queue<Token> m_tokens;

    // state info
    bool m_startedStream, m_endedStream;
    bool m_simpleKeyAllowed;
    bool m_canBeJSONFlow;
    std::stack<SimpleKey> m_simpleKeys;
    std::stack<IndentMarker*> m_indents;
    ptr_vector<IndentMarker> m_indentRefs; // for "garbage collection"
    std::stack<FLOW_MARKER> m_flows;
};
} // namespace YAML

#endif // SCANNER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
